/* 
 * Copyright (c) 2012 JSquirrelEngine
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in 
 * the Software without restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
 * Software, and to permit persons to whom the Software is furnished to do so, subject
 * to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 * 
 */

package org.jsquirrel.math;

/**
 * 
 * Class representing a closed interval with its lower and upper bounds stored as
 * float variables.
 * 
 * @author Juan David Adarve Bermudez
 *
 */
public class FInterval {

	//*********************************************************************************************
	// INSTANCE ATTRIBUTES
	//*********************************************************************************************
	
	/**
	 * Interval boundaries
	 */
	private float lowerBound, upperBound;
	
	
	//*********************************************************************************************
	// CONSTRUCTORS
	//*********************************************************************************************
	
	/**
	 * Creates an interval with lower and upper bounds equal to 0.0
	 */
	public FInterval() {
		lowerBound = 0.0f;
		upperBound = 0.0f;
	}
	
	/**
	 * Creates an interval with the given lower and upper bounds. In case the parameters
	 * does not met the condition lowerBound <= upperBound, this constructor makes the
	 * swap between the parameters
	 * 
	 * @param lowerBound: interval lower bound
	 * @param upperBound: interval upper bound
	 */
	public FInterval(float lowerBound, float upperBound) {
		
		if(lowerBound <= upperBound) {
			
			this.lowerBound = lowerBound;
			this.upperBound = upperBound;
			
		} else {
			
			this.lowerBound = upperBound;
			this.upperBound = lowerBound;
		}
			
		
	}
	
	/**
	 * Creates an interval with the values for the bounds taken from another FInterval instance.
	 * 
	 * @param other
	 */
	public FInterval(final FInterval other) {
		
		if(other == null) {
			throw new NullPointerException("FInterval.FInterval(FInterval other): null parameter");
		}
		
		lowerBound = other.lowerBound;
		upperBound = other.upperBound;
	}
	
	
	
	//*********************************************************************************************
	// PUBLIC METHODS
	//*********************************************************************************************
	
	/**
	 * @return returns the interval lower bound
	 */
	public final float lowerBound() {
		return lowerBound;
	}
	
	/**
	 * Sets the interval lower bound. This method does not check if the
	 * new value for the lower bound mets the condition lowerBound <= upperBound
	 * 
	 * @param lowerBound: new value for the lower bound
	 */
	public final void setLowerBound(float lowerBound) {
		this.lowerBound = lowerBound;
	}
	
	/**
	 * 
	 * @return the upper bound of the interval
	 */
	public final float upperBound() {
		return upperBound;
	}
	
	/**
	 * Sets the upper bound for this interval. This method does not check
	 * the condition lowerBound <= upperBound
	 * 
	 * @param upperBound: new value for the interval upper bound
	 */
	public final void setUpperBound(float upperBound) {
		this.upperBound = upperBound;
	}
	
	/**
	 * Method to be used to initialize the interval with the two bounds but when it is unknown which of the
	 * parameters is the lower and which one the upper bound.
	 * 
	 * @param b1: first bound
	 * @param b2: second bound
	 */
	public final void setBounds(float b1, float b2) {
		
		if(b1 < b2) {
			lowerBound = b1;
			upperBound = b2;
		} else {
			lowerBound = b2;
			upperBound = b1;
		}
	}
	
	/**
	 * Returns the length of the interval, i.e., the distance between the upper and lower bounds
	 * @return
	 */
	public final float length() {
		return upperBound - lowerBound;
	}
	
	
	/**
	 * Updates the boundaries of the interval for the v parameter being inside the interval.
	 * In case v is outside the interval, either the lower or upper bound are changed
	 * 
	 * @param v: value to which the interval must fit.
	 */
	public final void updateToFit(float v) {
		
		lowerBound = lowerBound < v? lowerBound : v;
		upperBound = upperBound > v? upperBound : v;
	}
	
	/**
	 * Tests whether the given parameter p is inside the interval. That is, test whether p
	 * meets the condition lowerBound <= p <= upperBound
	 * 
	 * @param p: value to test if it is inside the interval
	 * 
	 * @return true if p is inside the interval, false otherwise
	 */
	public final boolean pointInside(float p) {
		return lowerBound >= p && upperBound <= p;
	}
	
	
	/**
	 * Tests whether this interval intersects with another one given as parameter considering this
	 * and the parameter as closed intervals. The result of this method is a new interval with the
	 * region of intersection or null in case there is no intersection. If the parameter is null,
	 * the behavior of this method is undefined.
	 * 
	 * @param interval: interval to test intersection
	 * 
	 * @return a new interval with the intersection region or null in case there is no intersection.
	 */
	public final FInterval intersection(final FInterval interval) {
		
		// NOTATION: the boundaries of this interval are called a and b
		// the boundaries of the parameter are c and d
		boolean cInAB = interval.lowerBound - lowerBound >= 0.0f && interval.lowerBound - upperBound <= 0.0f;
		boolean dInAB = interval.upperBound - lowerBound >= 0.0f && interval.upperBound - upperBound <= 0.0f;
		boolean aInCD = lowerBound - interval.lowerBound >= 0.0f && lowerBound - interval.upperBound <= 0.0f;
		boolean bInCD = upperBound - interval.lowerBound >= 0.0f && upperBound - interval.upperBound <= 0.0f;
		
		// if there is intersection
		if(cInAB || dInAB || aInCD || bInCD) {
			
			FInterval returnInterval = new FInterval();
			
			returnInterval.lowerBound = cInAB ? interval.lowerBound : lowerBound;
			returnInterval.upperBound = dInAB ? interval.upperBound : upperBound;
			
			return returnInterval;
		}
		else {
			return null;
		}
	}
	
	/**
	 * Tests whether there is intersection between this and a given interval and stores the
	 * intersection region on another interval given as parameter. In case some of the
	 * behavior of this method is undefined.
	 * 
	 * @param interval: interval to which the intersection is tested.
	 * @param returnInterval: instance on which the intersection region is to be stored.
	 * 
	 * @return true if there is intersection, false otherwise
	 */
	public final boolean intersects(final FInterval interval, FInterval returnInterval) {
	
		// NOTATION: the boundaries of this interval are called a and b
		// the boundaries of the parameter are c and d
		boolean cInAB = interval.lowerBound - lowerBound >= 0.0f && interval.lowerBound - upperBound <= 0.0f;
		boolean dInAB = interval.upperBound - lowerBound >= 0.0f && interval.upperBound - upperBound <= 0.0f;
		boolean aInCD = lowerBound - interval.lowerBound >= 0.0f && lowerBound - interval.upperBound <= 0.0f;
		boolean bInCD = upperBound - interval.lowerBound >= 0.0f && upperBound - interval.upperBound <= 0.0f;
		
		// if there is intersection
		if(cInAB || dInAB || aInCD || bInCD) {
			
			// set the boundaries for the return interval
			returnInterval.lowerBound = cInAB ? interval.lowerBound : lowerBound;
			returnInterval.upperBound = dInAB ? interval.upperBound : upperBound;
			
			return true;
		}
		else {
			return false;
		}
	}
	
	/**
	 * Checks intersection between this interval and another one given as parameter. Both intervals
	 * are considered as closed intervals. If the parameter is null, the behavior is undefined.
	 * 
	 * @param interval: interval to check intersection against.
	 * 
	 * @return true in case of intersection, false otherwise
	 */
	public final boolean intersects(final FInterval interval) {
		
		// NOTATION: the boundaries of this interval are called a and b
		// the boundaries of the parameter are c and d
		boolean cInAB = interval.lowerBound - lowerBound >= 0.0f && interval.lowerBound - upperBound <= 0.0f;
		boolean dInAB = interval.upperBound - lowerBound >= 0.0f && interval.upperBound - upperBound <= 0.0f;
		boolean aInCD = lowerBound - interval.lowerBound >= 0.0f && lowerBound - interval.upperBound <= 0.0f;
		boolean bInCD = upperBound - interval.lowerBound >= 0.0f && upperBound - interval.upperBound <= 0.0f;
		
		return cInAB || dInAB || aInCD || bInCD;
		
	}
	
	/**
	 * Returns a String with the boundaries of the interval formated as "[l, u]" where l is the lower and u the
	 * upper bound.
	 * 
	 * @return String with the interval information.
	 */
	@Override
	public String toString() {
		return "[" + lowerBound + ", " + upperBound + "]";
	}
	
	
	//*********************************************************************************************
	// PUBLIC STATIC METHODS
	//*********************************************************************************************	
	
	/**
	 * Computes the intersection of two intervals, represented as two float values.
	 * 
	 * @param lower1: lower bound for the first interval
	 * @param upper1: upper bound for the first interval
	 * @param lower2: lower bound for the second interval
	 * @param upper2: upper bound for the second interval
	 * 
	 * @return true in case of intersection, false otherwise
	 */
	public static final boolean intersection(float lower1, float upper1, float lower2, float upper2) {
				
		// NOTATION: the boundaries of this interval are called a and b
		// the boundaries of the parameter are c and d
		boolean cInAB = lower2 - lower1 >= 0.0f && lower2 - upper1 <= 0.0f;
		boolean dInAB = upper2 - lower1 >= 0.0f && upper2 - upper1 <= 0.0f;
		boolean aInCD = lower1 - lower2 >= 0.0f && lower1 - upper2 <= 0.0f;
		boolean bInCD = upper1 - lower2 >= 0.0f && upper1 - upper2 <= 0.0f;
		
		return cInAB || dInAB || aInCD || bInCD;
	}
	
	/**
	 * Check for the intersection of two intervals considered as closed intervals. If any parameter
	 * is null, the behavior is undefined.
	 * 
	 * @param interval1: first interval
	 * @param interval2: second interval
	 * 
	 * @return true in case of intersection, false otherwise
	 */
	public static final boolean intersection(final FInterval interval1, final FInterval interval2) {
		
		boolean cInAB = interval2.lowerBound - interval1.lowerBound >= 0.0f && interval2.lowerBound - interval1.upperBound <= 0.0f;
		boolean dInAB = interval2.upperBound - interval1.lowerBound >= 0.0f && interval2.upperBound - interval1.upperBound <= 0.0f;
		boolean aInCD = interval1.lowerBound - interval2.lowerBound >= 0.0f && interval1.lowerBound - interval2.upperBound <= 0.0f;
		boolean bInCD = interval1.upperBound - interval1.lowerBound >= 0.0f && interval1.upperBound - interval2.upperBound <= 0.0f;
		
		return cInAB || dInAB || aInCD || bInCD;
	}
	
	/**
	 * Check whether or not a given point is inside the boundaries of an interval. In case the
	 * interval parameter is null, the behavior is undefined
	 * 
	 * @param interval
	 * @param p: point to check
	 * 
	 * @return true if p is inside the interval, false otherwise
	 */
	public static final boolean pointInside(final FInterval interval, float p) {
		
		return interval.lowerBound >= p && interval.upperBound <= p;
	}
	
	/**
	 * Check whether or not a given point is inside the boundaries of an interval, represented
	 * as two float values. This method does not check the condition lowerBound <= upperBound. If
	 * such condition is not met, the behavior is undefined.
	 * 
	 * @param lowerBound: interval lower bound
	 * @param upperBound: interval upper bound
	 * @param p: point to check
	 * 
	 * @return true if the p is inside the interval, false otherwise
	 */
	public static final boolean pointInside(float lowerBound, float upperBound, float p) {
		
		return lowerBound >= p && upperBound <= p;
	}
	
}
